/*
 * Copyright (c) 2019 Hitevision
 */

package tw.com.hitevision.whiteboard.android.fastdraw;

import static tw.com.hitevision.whiteboard.android.widgets.HighlighterMenuView.seek_alpha;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Shader;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Toast;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.collection.SparseArrayCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;

import com.hht.wbdraw.IDrawService;
import com.hht.wbjni.WbJniCall;
import com.internal.i.solution.touch.IsTouchManager;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import tw.com.hitevision.whiteboard.android.Device;
import tw.com.hitevision.whiteboard.android.Props;
import tw.com.hitevision.whiteboard.android.R;
import tw.com.hitevision.whiteboard.android.Whiteboard;
import tw.com.hitevision.whiteboard.android.display.DisplayDpiHelper;
import tw.com.hitevision.whiteboard.android.display.DisplayManager;
import tw.com.hitevision.whiteboard.android.render.Line;
import tw.com.hitevision.whiteboard.android.state.WhiteboardState;
import tw.com.hitevision.whiteboard.android.util.SystemPropertiesUtils;
import tw.com.hitevision.whiteboard.android.util.TouchUtils;
import tw.com.hitevision.whiteboard.android.widgets.HighlighterMenuView;


public class FastDraw {
    private static final String TAG = FastDraw.class.getSimpleName();

    private static final Bitmap.Config BMP_FORMAT = Props.getFlavorChipset().equals(Props.Flavor.Chipset.Optoma) ? Bitmap.Config.ARGB_4444 : Bitmap.Config.ARGB_8888;

    private static final int TYPE_STYLUS = 0;
    private static final int TYPE_FINGER = 1;

    private static final int MAX_POINTER_INPUT = 20;

    public static final String LOCK_FRAME_BUFFER_ON = "1";
    public static final String LOCK_FRAME_BUFFER_OFF = "0";

    private static final boolean ERASER_ENABLE = true;

    private static final float ANTIALIASING_EXTEND = 2f;

    public static boolean isHighliter = true;

    private volatile boolean isDrawing = false;

    private int activeCount;
    private final Context context;
    private final SparseArrayCompat<PointF> lastMoves;
    private final SparseArrayCompat<PointF> oldMoves;
    private final DisplayManager displayManager;

    private static IDrawService iDrawService;

    private Canvas bitmapCanvas;
    private Path[] paths;
    private Path[] completePaths;
    private Bitmap stylusBitmap;
    private Bitmap fingerBitmap;
    private BitmapPaint stylusStrokePaint;
    private BitmapPaint fingerStrokePaint;
    private ConcurrentLinkedDeque<PathPaint> pathsBufferQueue;
    private Canvas whiteboardCanvas;
    private int[] snakePathPointsSize;
    private long renderTime;
    private ScheduledExecutorService[] executorServicesByPointerId;
    private ScheduledExecutorService executorService;

    // Eraser
    private Bitmap fastDrawEraserBitmap;
    private BitmapPaint eraserPenPaint;
    private Paint eraserCircleDashLinePaint;
    private float eraserDiameter;
    private boolean partialEraserActive = false;
    private boolean objectEraserActive = false;
    private Paint objectEraserStrokePaint;
    private float dashLineWidth;
    private float[] lastEraserUp;
    private WhiteboardState whiteboardState;
    public static boolean needDraw = false;
    private boolean isAnnotationMode;

    public void setAnnotationMode(boolean annotationMode) {
        isAnnotationMode = annotationMode;
    }

    public boolean isAnnotationMode() {
        return isAnnotationMode;
    }

    int optoma4k2k = 1;



    public FastDraw(Context context, WhiteboardState whiteboardState, boolean isAnnotationMode) {
        this.context = context;
        this.whiteboardState = whiteboardState;
        this.isAnnotationMode = isAnnotationMode;
        lastMoves = new SparseArrayCompat<>();
        oldMoves = new SparseArrayCompat<>();
        displayManager = DisplayManager.getInstance();

//        if(Props.getFlavorChipset().equals(Props.Flavor.Chipset.Optoma))
//            optoma4k2k = 2;

//        if (WbJniCall.isLoaded()) {
        if (isSupportedCanvasFastDraw()) {
            WbJniCall.fbStart();
            // 811 hl
            paths = new Path[20];
            Arrays.fill(paths, new Path());

            completePaths = new Path[20];
            for (int i = 0; i < completePaths.length; i++) {
                completePaths[i] = new Path();
            }

            snakePathPointsSize = new int[20];
            Arrays.fill(snakePathPointsSize, 0);

            setCanvas(new Canvas());
            stylusStrokePaint = new BitmapPaint();
            fingerStrokePaint = new BitmapPaint();

            stylusBitmap = Bitmap.createBitmap(1, 1, BMP_FORMAT);
            fingerBitmap = Bitmap.createBitmap(1, 1, BMP_FORMAT);
            bitmapCanvas = new Canvas();

            pathsBufferQueue = new ConcurrentLinkedDeque<>();

            executorService = Executors.newScheduledThreadPool(1);

            executorServicesByPointerId = new ScheduledExecutorService[20];
            for (int i = 0; i < executorServicesByPointerId.length; i++) {
                executorServicesByPointerId[i] = Executors.newScheduledThreadPool(1);
            }
        }
    }

    public void setFastDrawEraserBitmap(Bitmap bitmap) {
        this.fastDrawEraserBitmap = bitmap;
    }

    public Bitmap getFastDrawEraserBitmap() {
        return fastDrawEraserBitmap;
    }

    Handler handler = new Handler() {
        @Override
        public void dispatchMessage(@NonNull Message msg) {
            if (msg.what == 100) { //9666
                removeMessages(100);
                WbJniCall.fbClear();
            }
        }
    };
    // For fb setCanvas() support
    // if the paint has been set bitmap shader, u can not set color directly.
    private static class BitmapPaint {
        int color;
        Paint paint;

        BitmapPaint() {
            paint = new Paint();
            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
            paint.setStrokeCap(Paint.Cap.ROUND);
            paint.setStrokeJoin(Paint.Join.ROUND);
            paint.setStyle(Paint.Style.STROKE);
            paint.setAntiAlias(true);
            paint.setDither(true);
        }
    }

    public void drawCircle(float x, float y, float r, Paint paint) {
        whiteboardCanvas.drawCircle(x, y, r, paint);
    }

    public void drawBitmap(Bitmap bmp, float x, float y, float w, float h, float limit, boolean isFirst, boolean drawCircle, Paint paint) {
        if (y >= 2160 - limit)
            return;
        if (y + h >= 2160 - limit)
            h = 2160 - limit - y;

        Rect rect = new Rect((int) x, (int) y, (int) (x + w), (int) (y + h));
//        whiteboardCanvas.save();
//        whiteboardCanvas.clipRect(rect);
        whiteboardCanvas.drawBitmap(bmp, rect, rect, paint);
//        whiteboardCanvas.restore();
    }

    // For fb setCanvas() support
    private Paint initSnakePaint() {
        Paint snakePaint = new Paint();
        snakePaint.setColor(Color.TRANSPARENT);
        snakePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        snakePaint.setStrokeCap(Paint.Cap.ROUND);
        snakePaint.setStrokeJoin(Paint.Join.ROUND);
        snakePaint.setStyle(Paint.Style.STROKE);
        snakePaint.setAntiAlias(true);
        snakePaint.setDither(true);
        return snakePaint;
    }

    // For fb setCanvas() support
    public void setRenderTime(long time) {
        this.renderTime = time;
    }

    // For fb setCanvas() support
    public void setSnakePathPointsLimit(int pointerId, long lastUpdateTime, List<Line.DiffPoint> points, int color, float thickness) {
        if (snakePathPointsSize != null &&
                snakePathPointsSize[pointerId] < points.size() && points.size() > 1
                && isDrawing) {
            Path snakePath = new Path();
            int current;
            if (snakePathPointsSize[pointerId] == 0) {
                snakePathPointsSize[pointerId] = 1;
            }
            current = snakePathPointsSize[pointerId] - 1;
            snakePath.moveTo(points.get(current).x / optoma4k2k, points.get(current).y / optoma4k2k);

            PointF last = new PointF(points.get(current).x, points.get(current).y);
            current++;

            while (current < points.size()) {
                float x = points.get(current).x;
                float y = points.get(current).y;
                snakePath.quadTo(last.x / optoma4k2k, last.y / optoma4k2k, (x + last.x) / 2 / optoma4k2k, (y + last.y) / 2 / optoma4k2k);
                last.x = x;
                last.y = y;
                current++;
                snakePathPointsSize[pointerId]++;
            }

            snakePath.lineTo(last.x / optoma4k2k, last.y / optoma4k2k);

            Paint snakePaint = initSnakePaint();
            snakePaint.setStrokeWidth(thickness + ANTIALIASING_EXTEND);

            executorServicesByPointerId[pointerId].schedule(new Callable<String>() {
                @Override
                public String call() {
                    if (isDrawing) {
                        whiteboardCanvas.drawPath(snakePath, snakePaint);
                    }
                    return pointerId + "";
                }
            }, 200 + (int) (renderTime * 2f), TimeUnit.MILLISECONDS);
        }
    }

    // For fb setCanvas() support
    private class PathPaint {
        Path path;
        Paint paint;

        PathPaint(Path p, float width) {
            path = new Path(p);
            paint = initSnakePaint();
            paint.setStrokeWidth(width);
            paint.setDither(true);
            paint.setAntiAlias(true);
        }

        PathPaint(Path p, Paint paint) {
            path = new Path(p);
            this.paint = new Paint(paint);
        }
    }

    // For fb setCanvas() support
    public void addConsumePath(Path path, float thickness, int color) {
        if (pathsBufferQueue != null && isColorTranslucent(color)) {
            pathsBufferQueue.add(new PathPaint(new Path(path), thickness + ANTIALIASING_EXTEND));
        }
    }

    // For fb setCanvas() support
    public void consumePaths() {
        if (pathsBufferQueue == null) {
            return;
        }
        PathPaint p;
        while ((p = pathsBufferQueue.poll()) != null) {
            final PathPaint pathPaint = new PathPaint(p.path, p.paint);
            executorService.schedule(new Runnable() {
                @Override
                public void run() {
                    whiteboardCanvas.drawPath(pathPaint.path, pathPaint.paint);
                }
            }, 200 + renderTime, TimeUnit.MILLISECONDS);
        }
        Arrays.fill(snakePathPointsSize, 0);
    }

    private boolean isColorTranslucent(@ColorInt int color) {
        return Props.getFlavorChipset().equals(Props.Flavor.Chipset.Amlogic) ? false : Color.alpha(color) < 255;
    }

    // For fb setCanvas() support
    public void setCanvas(Canvas canvas) {
        if (WbJniCall.isLoaded() && canvas != null) {
            whiteboardCanvas = canvas;
            WbJniCall.fbSetCanvas(whiteboardCanvas);
        }
    }

    // For fb setCanvas() support
    private void setShaderColor(@ColorInt int color, BitmapPaint bitmapPaint, Bitmap bitmap) {
        if (bitmapPaint.color != color) {
            bitmap.setConfig(Props.getFlavorChipset().equals(Props.Flavor.Chipset.Optoma) ?
                    Bitmap.Config.ARGB_4444 : Bitmap.Config.ARGB_8888);
            bitmapCanvas.setBitmap(bitmap);
            bitmapCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            bitmapCanvas.drawColor(color);
            BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
            bitmapPaint.paint.setShader(bitmapShader);
            bitmapPaint.color = color;
        }
    }

    public void setIDrawService(IDrawService iDrawService) {
        FastDraw.iDrawService = iDrawService;
    }

    public static boolean isSupported() {
        return (WbJniCall.isLoaded() || iDrawService != null) && (Props.isPaintHelperEnabled());
    }

    // For fb setCanvas() support
    public static boolean isSupportedCanvasFastDraw() {
        return WbJniCall.isLoaded() && Props.isPaintHelperEnabled()
                && (
                Props.getFlavorChipset().equals(Props.Flavor.Chipset.Hisi) || Props.getFlavorChipset().equals(Props.Flavor.Chipset.Amlogic) || Props.getFlavorChipset().equals(Props.Flavor.Chipset.Optoma)
        );
    }

    public static boolean isSupportedCanvasFastDraw_() {
        return WbJniCall.isLoaded() && Props.isPaintHelperEnabled() && Props.getFlavorChipset().equals(Props.Flavor.Chipset.Hisi);
    }

    public static boolean isSupportedCanvasFastDrawEraser() {
        return isSupportedCanvasFastDraw_() && Props.getFlavorModel().equals(Props.Flavor.Model.Oem) && ERASER_ENABLE;
    }

    public boolean isDrawing() {
        return isDrawing;
    }

    public void updateEraser(Device device, Whiteboard.Input input) {
        if (isSupportedCanvasFastDrawEraser()) {
            partialEraserActive = false;
            objectEraserActive = false;
            if (eraserPenPaint == null) {
                eraserPenPaint = new BitmapPaint();
                eraserPenPaint.paint.setColor(0xFFFFFFFF);
                eraserPenPaint.paint.setAntiAlias(true);
                eraserPenPaint.paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
            }
            if (input == Whiteboard.Input.PARTIAL_ERASER) {
                if (eraserCircleDashLinePaint == null) {
                    final float factor = DisplayDpiHelper.getInstance().getFake4kFactor();
                    eraserCircleDashLinePaint = new Paint();
                    eraserCircleDashLinePaint.setColor(context.getResources().getColor(R.color.eraser_oem_dashline_color));
                    eraserCircleDashLinePaint.setStyle(Paint.Style.STROKE);
                    dashLineWidth = context.getResources().getDimensionPixelSize(R.dimen.oem_eraser_circle_default_stroke_width);
                    eraserCircleDashLinePaint.setStrokeWidth(dashLineWidth);
                    eraserCircleDashLinePaint.setPathEffect(new DashPathEffect(new float[]{4f * factor, 4f * factor}, -1f * factor));
                    eraserCircleDashLinePaint.setAntiAlias(true);
                }
                if (fastDrawEraserBitmap != null && !fastDrawEraserBitmap.isRecycled()) {
                    BitmapShader bitmapShader = new BitmapShader(fastDrawEraserBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
                    eraserPenPaint.paint.setShader(bitmapShader);
                    eraserDiameter = device == Device.PALM ? context.getResources().getDimensionPixelSize(R.dimen.oem_eraser_circle_palm_diameter)
                            : context.getResources().getDimensionPixelSize(R.dimen.oem_eraser_circle_default_diameter);
                    eraserPenPaint.paint.setStrokeWidth(eraserDiameter);
                    partialEraserActive = true;
                }
            } else if (input == Whiteboard.Input.OBJECT_ERASER) {
                float strokeWidth = context.getResources().getDimensionPixelSize(R.dimen.eraser_vertical_offset);
                float factor = DisplayDpiHelper.getInstance().getFake4kFactor();
                if (objectEraserStrokePaint == null) {
                    objectEraserStrokePaint = new Paint();
                    objectEraserStrokePaint.setColor(ContextCompat.getColor(context, R.color.eraserTrack));
                    objectEraserStrokePaint.setStyle(Paint.Style.STROKE);
                    objectEraserStrokePaint.setStrokeCap(Paint.Cap.ROUND);
                    objectEraserStrokePaint.setStrokeJoin(Paint.Join.ROUND);
                    objectEraserStrokePaint.setPathEffect(new DashPathEffect(new float[]{3f * factor, 6f * factor, 9f * factor, 6f * factor}, -2f * factor));
                    objectEraserStrokePaint.setAntiAlias(true);
                    objectEraserStrokePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
                    Bitmap bitmap = Bitmap.createBitmap(1, 1, BMP_FORMAT);
                    Canvas canvas = new Canvas(bitmap);
                    canvas.drawColor(ContextCompat.getColor(context, R.color.eraserTrack));
                    objectEraserStrokePaint.setShader(new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
                    objectEraserStrokePaint.setStrokeWidth(strokeWidth);
                }
                objectEraserActive = true;
                eraserDiameter = strokeWidth / 2f;
            }
        }
    }

    public void start(MotionEvent event) throws RemoteException {
        if (!isSupported() /*|| (Props.getFlavorChipset().equals(Props.Flavor.Chipset.Amlogic) && isAnnotationMode)*/) {
            return;
        }
//        log("start");
//        log("SystemPropertiesUtils KEY_FRAME_BUFFER_LOCK:" + SystemPropertiesUtils.getStringValue(SystemPropertiesUtils.KEY_FRAME_BUFFER_LOCK, LOCK_FRAME_BUFFER_OFF));
        if (Props.getFlavorChipset().equals(Props.Flavor.Chipset.Amlogic)) {
            if(!SystemPropertiesUtils.getStringValue(SystemPropertiesUtils.KEY_FRAME_BUFFER_LOCK, LOCK_FRAME_BUFFER_OFF).equals(LOCK_FRAME_BUFFER_ON)) {
                SystemPropertiesUtils.setValue(SystemPropertiesUtils.KEY_FRAME_BUFFER_LOCK, LOCK_FRAME_BUFFER_ON);
                WbJniCall.fbStartDraw();//加速库在调fbStartDraw时自动锁屏，不需应用锁
//            WbJniCall.fbClear();
            }
        } else{
            SystemPropertiesUtils.setValue(SystemPropertiesUtils.KEY_FRAME_BUFFER_LOCK, LOCK_FRAME_BUFFER_ON);
        }
        handler.removeMessages(100);
        isDrawing = true;

        onTouch(event);
    }

    public void stop() {
        if (!isSupported()/* || (Props.getFlavorChipset().equals(Props.Flavor.Chipset.Amlogic) && isAnnotationMode)*/) {
            return;
        }

        SystemPropertiesUtils.setValue(SystemPropertiesUtils.KEY_FRAME_BUFFER_LOCK, LOCK_FRAME_BUFFER_OFF);

        Log.d("wx","FastDraw: stop() unlock fb");

        if (WbJniCall.isLoaded()){
            if(Props.getFlavorChipset().equals(Props.Flavor.Chipset.Hisi) || Props.getFlavorChipset().equals(Props.Flavor.Chipset.Optoma)) {
                handler.removeMessages(100);
                handler.sendEmptyMessageDelayed(100, 60);
            }else if(Props.getFlavorChipset().equals(Props.Flavor.Chipset.Amlogic)){
                WbJniCall.fbRefresh(10);
                handler.sendEmptyMessageDelayed(100, 250);
            }
        }

        if (snakePathPointsSize != null && isSupportedCanvasFastDraw()) {
            Arrays.fill(snakePathPointsSize, 0);
        }

        if (isSupportedCanvasFastDrawEraser()) {
            lastEraserUp = null;
        }

        isDrawing = false;
    }

    public void close() throws RemoteException {
        if (isDrawing) {
            log("close");
            stop();
        }

        if (WbJniCall.isLoaded()) {
            WbJniCall.fbStop();
        }
    }

    public void setWidth(Device device, float width) throws RemoteException {
        if (Props.getFlavorChipset().equals(Props.Flavor.Chipset.Optoma))
            width = width / optoma4k2k;
        if (!isSupported()) {
            return;
        }

        log("setWidth: device=" + device + ", width=" + width);
        final int type = device == Device.STYLUS ? TYPE_STYLUS : TYPE_FINGER;
        if (WbJniCall.isLoaded()) {
//            WbJniCall.fbSetPaintStrokeWidth(width, type);
            if (isSupportedCanvasFastDraw()) {// For fb setCanvas() support
                (device == Device.STYLUS ? stylusStrokePaint : fingerStrokePaint).paint.setStrokeWidth(width);
            } else {
                WbJniCall.fbSetPaintStrokeWidth(width, type);
            }
        } else if (iDrawService != null) {
            iDrawService.fbSetPaintStrokeWidth(width, type);
        }
    }

    public void setColor(Device device, @ColorInt int color) throws RemoteException {
        if (!isSupported()) {
            return;
        }
        log("setColor: device=" + device + ", color=" + color);
        final int type = device == Device.STYLUS ? TYPE_STYLUS : TYPE_FINGER;

        if (Props.getFlavorChipset().equals(Props.Flavor.Chipset.Mstar)/* || Props.getFlavorChipset().equals(Props.Flavor.Chipset.Optoma)*/) {
            color = ColorUtils.setAlphaComponent(color, 255); // transparency is not supported
        }

        // apply blue light filter if enabled
        log("isAnnotationMode=" + this.isAnnotationMode());
        if (this.isAnnotationMode() && displayManager.isBluelightFilterEnabled(context)) {
            color = displayManager.getBluelightFilteredColor(context, color);
            color = ColorUtils.setAlphaComponent(color, 255);
        }

        if (WbJniCall.isLoaded()) {
            if (isSupportedCanvasFastDraw()) {
                BitmapPaint bitmapPaint = device == Device.STYLUS ? stylusStrokePaint : fingerStrokePaint;
                Bitmap bitmap = device == Device.STYLUS ? stylusBitmap : fingerBitmap;
                if (bitmap.isRecycled()) {
                    bitmap = Bitmap.createBitmap(1, 1, BMP_FORMAT);
                }
                setShaderColor(color, bitmapPaint, bitmap);
            } else {
                WbJniCall.fbSetPaintColor(color, type);
            }
        } else if (iDrawService != null) {
            iDrawService.fbSetPaintColor(color, type);
        }
    }

    private int getEventDeviceType(int pointerIndex, MotionEvent event) {
        final Device device = TouchUtils.getEventDevice(context, pointerIndex, event);
        return device == Device.STYLUS ? TYPE_STYLUS : TYPE_FINGER;
    }

    public void onTouch(MotionEvent event) throws RemoteException {
        if (!isSupported()) {
            return;
        }

        if (event.getActionIndex() > activeCount) {
            return;
        }

        final int action = event.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                final int pointerIndex = 0;
                activeCount = 1;
                onTouchDown(pointerIndex, event);
                break;
            }
            case MotionEvent.ACTION_POINTER_DOWN: {
                final int pointerIndex = event.getActionIndex();
                activeCount++;
                if (pointerIndex >= MAX_POINTER_INPUT) {
                    activeCount--;
                    break;
                }
                onTouchDown(pointerIndex, event);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                final int pointerCount = event.getPointerCount();
                final int historySize = event.getHistorySize();

                for (int h = 0; h < historySize; h++) {
                    for (int i = 0; i < pointerCount; i++) {
                        final float x = event.getHistoricalX(i, h);
                        final float y = event.getHistoricalY(i, h);

                        onTouchMove(i, x, y, event);
                    }
                }

                for (int i = 0; i < pointerCount; i++) {
                    final float x = event.getX(i);
                    final float y = event.getY(i);
                    onTouchMove(i, x, y, event);
                }
                break;
            }
            case MotionEvent.ACTION_POINTER_UP: {
                final int pointerIndex = event.getActionIndex();
                if (pointerIndex >= activeCount) {
                    break;
                }
                activeCount--;
                onTouchUp(pointerIndex, event);
                break;
            }
            case MotionEvent.ACTION_UP: {
                final int pointerIndex = 0;
                if (activeCount == 0) {
                    break;
                }
                onTouchUp(pointerIndex, event);
                break;
            }
        }
    }

    private void onTouchDown(int pointerIndex, MotionEvent event) throws RemoteException {
        if (!isSupported()) {
            return;
        }

        final int pointerId = event.getPointerId(pointerIndex);
        final float x = event.getX(pointerIndex);
        final float y = event.getY(pointerIndex);
        final int deviceType = getEventDeviceType(pointerIndex, event);
        final Device device = TouchUtils.getEventDevice(context, pointerIndex, event);
        log("onTouchDown: pointerIndex=" + pointerIndex + ", device : " + device);

        if ((partialEraserActive || objectEraserActive) && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
            return;
        }

        if (lastMoves.containsKey(pointerId)) {
            lastMoves.get(pointerId).set(x, y);
            oldMoves.get(pointerId).set(x, y);
        } else {
            lastMoves.put(pointerId, new PointF(x, y));
            oldMoves.put(pointerId, new PointF(x, y));
        }


        if (WbJniCall.isLoaded()) {
            if (isSupportedCanvasFastDraw()) {
                fbCanvasTouchDown(pointerId, x, y, device);
            } else {
                WbJniCall.fbDrawPoint(x, y, deviceType);
                WbJniCall.fbMoveTo(x, y, pointerId, deviceType);
            }
        } else if (iDrawService != null) {
            iDrawService.fbDrawPoint(x, y, deviceType);
            iDrawService.fbMoveTo(x, y, pointerId, deviceType);
        }
    }

    private void onTouchMove(int pointerIndex, float x, float y, MotionEvent event) throws RemoteException {
        if (!isSupported()) {
            return;
        }

        final int pointerId = event.getPointerId(pointerIndex);
        final int deviceType = getEventDeviceType(pointerIndex, event);

        final PointF last = lastMoves.get(pointerId);
        final PointF old = oldMoves.get(pointerId);
        if (last != null && isSupportedCanvasFastDraw() && !allowPoint(x, y, last.x, last.y)) {
            return;
        }

        int dist = (Props.getMainboardVersion() == Props.Mainboard.V_828 ? 3 : 1);
        if (last != null && (Math.abs(last.x - x) > dist || Math.abs(last.y - y) > dist)) {
            if (WbJniCall.isLoaded()) {
                if (isSupportedCanvasFastDraw()) {
                    final Device device = TouchUtils.getEventDevice(context, pointerIndex, event);
//                    fbCanvasTouchMove(pointerId, old,last, x, y, device);
//                    fbCanvasTouchMove(pointerId, last, x, y, device);
                    fbCanvasTouchMove(pointerId, old, last, x, y, device);
                } else {
                    WbJniCall.fbQuadto(last.x, last.y, (x + last.x) / 2, (y + last.y) / 2, pointerId, deviceType);
                }
            } else if (iDrawService != null) {
                iDrawService.fbQuadto(last.x, last.y, (x + last.x) / 2, (y + last.y) / 2, pointerId, deviceType);
            }
            old.set(last);
            last.set(x, y);
        }
    }

    private void fbCanvasTouchMove(int pointerId, PointF last, float x, float y, Device device) {
        Log.d("points", "Last:" + (int) last.x + "," + (int) last.y + "   Current:" + (int) x + "," + (int) y);
//        Path path = paths[pointerId];
//        path.moveTo(last.x / optoma4k2k, last.y / optoma4k2k);
//        path.quadTo(last.x / optoma4k2k, last.y / optoma4k2k, (last.x + last.x)/2 / optoma4k2k, (last.y +last.y) / 2 / optoma4k2k);
//        path.moveTo(last.x / optoma4k2k, last.y / optoma4k2k);
        paths[pointerId].moveTo(last.x / optoma4k2k, last.y / optoma4k2k);
//        paths[pointerId].moveTo((old.x+last.x)/2 / optoma4k2k, (old.y+last.y)/2 / optoma4k2k);
        paths[pointerId].quadTo(last.x / optoma4k2k, last.y / optoma4k2k, (x + last.x) / 2 / optoma4k2k, (y + last.y) / 2 / optoma4k2k);
//        paths[pointerId].quadTo(last.x / optoma4k2k, last.y / optoma4k2k, (x + last.x) / 2 / optoma4k2k, (y + last.y) / 2 / optoma4k2k);
//        path.lineTo(x / 2, y / 2);
        completePaths[pointerId].moveTo(last.x / optoma4k2k, last.y / optoma4k2k);
//        Path linkingPath = completePaths[pointerId];
//        linkingPath.moveTo(last.x / optoma4k2k, last.y / optoma4k2k);
//        linkingPath.lineTo(x / optoma4k2k, y / optoma4k2k);
//        completePaths[pointerId].moveTo((old.x+last.x)/2 / optoma4k2k, (old.y+last.y)/2 / optoma4k2k);
        completePaths[pointerId].quadTo(last.x / optoma4k2k, last.y / optoma4k2k, (x + last.x) / 2 / optoma4k2k, (y + last.y) / 2 / optoma4k2k);
//        paths[pointerId].moveTo((last.x + x) / 2 / optoma4k2k, (y + last.y) / 2 / optoma4k2k);
//        completePaths[pointerId].moveTo((last.x + x) / 2 / optoma4k2k, (y + last.y) / 2 / optoma4k2k);

        if ((partialEraserActive || objectEraserActive) && pointerId != 0) {
            return;
        }

        if (whiteboardCanvas != null) {
            if (partialEraserActive || objectEraserActive) {
                if (partialEraserActive) {
                    whiteboardCanvas.drawPath(paths[pointerId], eraserPenPaint.paint);
                    whiteboardCanvas.drawCircle(x, y, eraserDiameter / 2f - dashLineWidth, eraserCircleDashLinePaint);
                } else {
                    whiteboardCanvas.drawPath(paths[pointerId], objectEraserStrokePaint);
                }
            } else {
                whiteboardCanvas.drawPath(paths[pointerId], device == Device.STYLUS ? stylusStrokePaint.paint : fingerStrokePaint.paint);
            }
            paths[pointerId].rewind();
        }
    }

    private void onTouchUp(int pointerIndex, MotionEvent event) throws RemoteException {
        if (!isSupported()) {
            return;
        }

        final int pointerId = event.getPointerId(pointerIndex);
        final float x = event.getX(pointerIndex);
        final float y = event.getY(pointerIndex);
        final int deviceType = getEventDeviceType(pointerIndex, event);

        if (WbJniCall.isLoaded()) {
            if (isSupportedCanvasFastDraw()) {
                final PointF last = lastMoves.get(pointerId);
                if (last != null) {
                    final Device device = TouchUtils.getEventDevice(context, pointerIndex, event);
                    fbCanvasTouchUp(pointerId, last, x, y, device);
                }

            } else {
//                WbJniCall.fbLineTo(x, y, pointerId, deviceType);
                WbJniCall.fbQuadto(x, y, x, y, pointerId, deviceType);
            }
        } else if (iDrawService != null) {
            // use quad to due to line to won't work in library
//            iDrawService.fbLineTo(x, y, pointerId, deviceType);
            iDrawService.fbQuadto(x, y, x, y, pointerId, deviceType);
        }
        lastMoves.remove(pointerId);
    }

    // For 811 setCanvas() support
    private void fbCanvasTouchDown(int pointerId, float x, float y, Device device) {
        handler.removeMessages(100);
        if (FastDraw.isHighliter) {
            if (Props.getFlavorModel().equals(Props.Flavor.Model.Oem)) {
                if (device == Device.STYLUS) {
                    stylusStrokePaint.paint.setAlpha((seek_alpha));
                    whiteboardCanvas.drawPoint(x, y, stylusStrokePaint.paint);
                } else {
                    fingerStrokePaint.paint.setAlpha((int) (seek_alpha));
                    whiteboardCanvas.drawPoint(x, y, fingerStrokePaint.paint);
                }
            } else {

            }
        } else {
            stylusStrokePaint.paint.setAlpha(255);
            fingerStrokePaint.paint.setAlpha(255);
            if (device == Device.STYLUS) {
                whiteboardCanvas.drawPoint(x, y, stylusStrokePaint.paint);
            } else {
                whiteboardCanvas.drawPoint(x, y, fingerStrokePaint.paint);
            }
        }
        Path path = paths[pointerId];
        path.rewind();
        path.moveTo(x, y);
//        path.lineTo(x, y);
        Path linkingPath = completePaths[pointerId];
        linkingPath.rewind();
        linkingPath.moveTo(x, y);

        if (whiteboardCanvas != null) {
            path.rewind();
            if (lastEraserUp != null) {
                if (eraserPenPaint.paint != null) {
                    whiteboardCanvas.drawCircle(lastEraserUp[0], lastEraserUp[1], lastEraserUp[2] / 2f, eraserPenPaint.paint);
                }
            }
        }
    }

    // For 811 setCanvas() support
    private void fbCanvasTouchMove(int pointerId, PointF old, PointF last, float x, float y, Device device) {
//        Log.d("points", "Last:"+(int)last.x+","+(int)last.y+"   Current:"+(int)x+","+(int)y);
//        Path path = paths[pointerId];
//        path.moveTo(last.x / optoma4k2k, last.y / optoma4k2k);
//        path.quadTo(last.x / optoma4k2k, last.y / optoma4k2k, (last.x + last.x)/2 / optoma4k2k, (last.y +last.y) / 2 / optoma4k2k);
//        path.moveTo(last.x / optoma4k2k, last.y / optoma4k2k);
//        paths[pointerId].moveTo(last.x / optoma4k2k, last.y / optoma4k2k);
        paths[pointerId].moveTo((old.x + last.x) / 2 / optoma4k2k, (old.y + last.y) / 2 / optoma4k2k);
        paths[pointerId].quadTo(last.x / optoma4k2k, last.y / optoma4k2k, (x + last.x) / 2 / optoma4k2k, (y + last.y) / 2 / optoma4k2k);
//        paths[pointerId].quadTo(last.x / optoma4k2k, last.y / optoma4k2k, (x + last.x) / 2 / optoma4k2k, (y + last.y) / 2 / optoma4k2k);
//        path.lineTo(x / 2, y / 2);
//        completePaths[pointerId].moveTo(last.x / optoma4k2k, last.y / optoma4k2k);
//        Path linkingPath = completePaths[pointerId];
//        linkingPath.moveTo(last.x / optoma4k2k, last.y / optoma4k2k);
//        linkingPath.lineTo(x / optoma4k2k, y / optoma4k2k);
        completePaths[pointerId].moveTo((old.x + last.x) / 2 / optoma4k2k, (old.y + last.y) / 2 / optoma4k2k);
        completePaths[pointerId].quadTo(last.x / optoma4k2k, last.y / optoma4k2k, (x + last.x) / 2 / optoma4k2k, (y + last.y) / 2 / optoma4k2k);
//        paths[pointerId].moveTo((last.x + x) / 2 / optoma4k2k, (y + last.y) / 2 / optoma4k2k);
//        completePaths[pointerId].moveTo((last.x + x) / 2 / optoma4k2k, (y + last.y) / 2 / optoma4k2k);

        if ((partialEraserActive || objectEraserActive) && pointerId != 0) {
            return;
        }

        if (whiteboardCanvas != null) {
            if (partialEraserActive || objectEraserActive) {
                if (partialEraserActive) {
                    whiteboardCanvas.drawPath(paths[pointerId], eraserPenPaint.paint);
                    whiteboardCanvas.drawCircle(x, y, eraserDiameter / 2f - dashLineWidth, eraserCircleDashLinePaint);
                } else {
                    whiteboardCanvas.drawPath(paths[pointerId], objectEraserStrokePaint);
                }
            } else {
                whiteboardCanvas.drawPath(paths[pointerId], device == Device.STYLUS ? stylusStrokePaint.paint : fingerStrokePaint.paint);
            }
            paths[pointerId].rewind();
        }
    }

    // For 811 setCanvas() support
    private void fbCanvasTouchUp(int pointerId, PointF last, float x, float y, Device device) {
        Path path = paths[pointerId];
        path.moveTo(last.x, last.y);
//        path.quadTo(last.x, last.y, last.x + (x - last.x) / 2, last.y + (y - last.y) / 2);
//        path.lineTo(x, y);
        path.quadTo(last.x, last.y, last.x + x, y);

        Path linkingPath = completePaths[pointerId];
        linkingPath.moveTo(last.x, last.y);
//        path.quadTo(last.x, last.y, last.x + (x - last.x) / 2, last.y + (y - last.y) / 2);
//        linkingPath.lineTo(x, y);
        path.quadTo(last.x, last.y, x, y);

        if (whiteboardCanvas != null) {
            path.rewind();

            if (partialEraserActive) {
                whiteboardCanvas.drawPath(linkingPath, eraserPenPaint.paint);
                whiteboardCanvas.drawCircle(x, y, eraserDiameter / 2f - dashLineWidth, eraserCircleDashLinePaint);
                lastEraserUp = new float[]{last.x, last.y, eraserPenPaint.paint.getStrokeWidth()};
            } else if (objectEraserActive) {
                whiteboardCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            }
        }
    }

    public boolean isPartialEraserActive() {
        return partialEraserActive;
    }

    // allowPoint() from Line class, this function should be moved to the touchListener of surfaceView in MainActivity.
    private boolean allowPoint(float x, float y, float lastX, float lastY) {
        float filterDistanceSquared = 4;
        final float dx = x - lastX;
        final float dy = y - lastY;
        return (dx * dx + dy * dy) > filterDistanceSquared;
    }

    private static void log(String msg) {
        Log.d(TAG, msg);
    }
}
